/**
 * 
 */
package org.going.annotation;

import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletContext;

import org.going.exception.GoingException;
import org.going.invoker.WebFunction;
import org.going.invoker.WebFunctionImpl;
import org.going.utils.GoingConst;
import org.going.utils.StringUtils;

/**
 * @author ferendo.huang@gmail.com
 * 
 */
public class AnnotationHelper implements GoingConst
{
	private static final Logger log = Logger.getLogger(AnnotationHelper.class.getName());

	private static final ClassByteCodeFilter filter = new ClassByteCodeFilter();

	public static void initialize(ServletContext servletContext) throws ClassNotFoundException, InstantiationException,
			IllegalAccessException
	{
		initializeAnnotationConfig(servletContext);
		List<Class<? extends Object>> list = new ArrayList<Class<? extends Object>>();
		// find annotation in /WEB-INFO/classes/
		// TODO: Currently scan web class only...
		scanWebClasses(servletContext, WEB_CLASSES_PREFIX, list);
		// build configuration for WebObject class...
		buildWebObjectClassConfig(servletContext, list);
		list.clear();
	}

	private static void initializeAnnotationConfig(ServletContext servletContext)
	{
		Set<String> bcan = new HashSet<String>(10, 1f);
		bcan.add("L" + WebObject.class.getName() + ";");
		servletContext.setAttribute(WEB_ANNOTATION_NAME_LIST, Collections.unmodifiableSet(bcan));
	}

	@SuppressWarnings("unchecked")
	private static void scanWebClasses(ServletContext servletContext, String prefix, List<Class<? extends Object>> list)
			throws ClassNotFoundException
	{

		ClassLoader loader = getClassLoader();

		Set<String> paths = servletContext.getResourcePaths(prefix);
		if (paths == null)
		{
			return; // need this in case there is no WEB-INF/classes directory
		}
		if (log.isLoggable(Level.FINEST))
		{
			log.finest("webClasses(" + prefix + ") - Received " + paths.size() + " paths to check");
		}

		String path = null;

		if (paths.isEmpty())
		{
			//
		}
		else
		{
			for (Object pathObject : paths)
			{
				path = (String) pathObject;
				if (path.endsWith("/"))
				{
					scanWebClasses(servletContext, path, list);
				}
				else if (path.endsWith(".class"))
				{
					DataInputStream in = null;
					boolean couldContainAnnotation = false;
					try
					{
						in = new DataInputStream(servletContext.getResourceAsStream(path));
						couldContainAnnotation = filter.containAnnotationsOnClassDef(in,
								(Set<String>) servletContext.getAttribute(WEB_ANNOTATION_NAME_LIST));
					}
					catch (IOException e)
					{
						// Include this class - we can't scan this class using
						// the filter, but it could be valid, so we need to
						// load it using the classLoader. Anyway, log a debug
						// message.
						couldContainAnnotation = true;
						if (log.isLoggable(Level.FINE))
						{
							log.fine("IOException when filtering class " + path + " for annotations");
						}
					}
					finally
					{
						if (in != null)
							try
							{
								in.close();
							}
							catch (IOException e)
							{
								// No Op
							}
					}

					if (couldContainAnnotation)
					{
						// Load it and add it to list for later processing
						path = path.substring(WEB_CLASSES_PREFIX.length()); // Strip
																			// prefix
						path = path.substring(0, path.length() - 6); // Strip
																		// suffix
						path = path.replace(URL_SEP, DOT); // Convert to FQCN

						Class<? extends Object> clazz = null;
						try
						{
							clazz = loader.loadClass(path);
						}
						catch (NoClassDefFoundError e)
						{
							; // Skip this class - we cannot analyze classes we
								// cannot load
						}
						catch (Exception e)
						{
							; // Skip this class - we cannot analyze classes we
								// cannot load
						}
						if (clazz != null)
						{
							list.add(clazz);
						}
					}
				}
			}
		}
	}

	private static void buildWebObjectClassConfig(ServletContext servletContext, List<Class<? extends Object>> list)
			throws InstantiationException, IllegalAccessException
	{
		Map<String, WebFunction> functionMap = new HashMap<String, WebFunction>();
		for (Class<? extends Object> clazz : list)
		{
			WebObject annotation = clazz.getAnnotation(WebObject.class);
			if (annotation == null)
				continue;
			String className = null;
			if (StringUtils.hasContent(annotation.name()))
			{
				className = annotation.name();
			}
			else
			{
				className = clazz.getSimpleName().toLowerCase();
			}
			for (Method method : clazz.getDeclaredMethods())
			{
				if (method.getModifiers() == Modifier.PUBLIC)
				{
					functionMap.put(className + DOT + method.getName(),
							new WebFunctionImpl(clazz.newInstance(), method));
				}
			}
		}
		servletContext.setAttribute(WEB_FUNCTION_MAP, Collections.unmodifiableMap(functionMap));
	}

	private static ClassLoader getContextClassLoader()
	{
		if (System.getSecurityManager() != null)
		{
			try
			{
				ClassLoader cl = AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>()
				{
					public ClassLoader run() throws PrivilegedActionException
					{
						return Thread.currentThread().getContextClassLoader();
					}
				});
				return cl;
			}
			catch (PrivilegedActionException pae)
			{
				throw new GoingException(pae);
			}
		}
		else
		{
			return Thread.currentThread().getContextClassLoader();
		}
	}

	private static ClassLoader getClassLoader()
	{
		ClassLoader loader = getContextClassLoader();
		if (loader == null)
		{
			loader = AnnotationHelper.class.getClassLoader();
		}
		return loader;
	}

	public static void main(String[] args)
	{
		System.out.println("L" + WebObject.class.getName() + ";");
	}
}
